import { FaceLandmarker, type FaceDetectorOptions } from '@mediapipe/tasks-vision'
import { createInstance } from 'localforage'
import assignWith from 'lodash/assignWith'
import type { Options } from '@/try-on/config'
import type { WasmFileset } from '@type/index'
export class Mediapipe {
  mediapipe: Promise<FaceLandmarker> | undefined
  private progressCallback: ((progress: number) => void)[] = []
  private downloadLength = [0, 0, 0]
  private localForage: LocalForage
  private options: Options
  constructor(options: Options) {
    this.options = options
    const localforageOption = options.localforage!
    this.localForage = createInstance(localforageOption)
    this.loadModule()
  }
  private async loadModule() {
    if (this.mediapipe) return this.mediapipe

    this.mediapipe = new Promise(async (resolve, reject) => {
      try {
        const { js, wasm, task } = this.options.filesPath!
        const downloadPathArr = [js, wasm, task]
        const filesPromise = await Promise.allSettled(
          downloadPathArr.map((item, index) => this.download(item!, index)),
        )
        const downloadSuccess = filesPromise.every((item) => item.status == 'fulfilled')
        if (downloadSuccess) {
          const wasmFileset: WasmFileset = {
            wasmLoaderPath: filesPromise[0].value,
            wasmBinaryPath: filesPromise[1].value,
          }
          const mediapipeOption: FaceDetectorOptions = {
            baseOptions: {
              modelAssetPath: filesPromise[2].value,
            },
          }
          const newConfig = assignWith<FaceDetectorOptions, FaceDetectorOptions>(
            this.options.Mediapipe!,
            mediapipeOption,
            (obj, source) => ({ ...obj, ...source }),
          )
          const FaceLandmarker = await this.init(wasmFileset, newConfig)
          resolve(FaceLandmarker)
        } else {
          reject('Model download failed!!!')
        }
      } catch (error) {
        reject(error)
      }
    })
    return this.mediapipe
  }
  private download(path: string, index: number) {
    return new Promise<string>(async (resove, reject) => {
      const fileName = path.split('/').pop()!
      const blob = await this.localForage.getItem<Blob>(fileName)
      if (blob) {
        const url = URL.createObjectURL(blob)
        this.downloadLength[index] = blob.size
        this.progress()
        return resove(url)
      }
      const xhr = new XMLHttpRequest()
      xhr.onload = function (this: Mediapipe) {
        if (xhr.status == 200) {
          const type = xhr.getResponseHeader('Content-Type')!
          const blob = new Blob([xhr.response], {
            type,
          })
          const url = URL.createObjectURL(blob)
          this.localForage.setItem(fileName!, blob)
          resove(url)
        } else {
          reject('Model download failed!!!')
        }
      }.bind(this)
      xhr.onerror = function () {
        reject('Model download failed!!!')
      }
      xhr.onprogress = function (this: Mediapipe, event: ProgressEvent) {
        this.downloadLength[index] = event.loaded
        this.progress()
      }.bind(this)
      xhr.open('GET', path, true)
      xhr.responseType = 'blob'
      xhr.send()
    })
  }
  private init(wasmFileset: WasmFileset, option: FaceDetectorOptions) {
    return FaceLandmarker.createFromOptions(wasmFileset, option)
  }
  onProgress(callback: (progress: number) => void) {
    this.progressCallback.push(callback)
  }
  private progress() {
    const { js, wasm, task } = this.options.filesBytes
    const totalSize = js + wasm + task
    const totalDownloadSize = this.downloadLength.reduce((pre, cur) => pre + cur, 0)
    const originProgress = (totalDownloadSize / totalSize) * 100
    const progress = Number(originProgress.toFixed(0))
    this.progressCallback.forEach((item) => item(progress))
  }
}
